namespace Wilco.CodeHilighter.Engine.SyntaxHighlighting
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    using System.Xml.Linq;

    /// <summary>
    /// Represents a word scanner.
    /// </summary>
    public class WordScanner : ScannerBase
    {
        #region Fields

        private string lastToken;
        private WordNode[] wordNodes;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.WordScanner"/> class.
        /// </summary>
        public WordScanner()
            : this(null, null)
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.WordScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        public WordScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
            : this(tokenizer, scannerResult, new WordNode[0])
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.WordScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <param name="wordNodes">An array of <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.WordNode"/> objects.</param>
        public WordScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult, WordNode[] wordNodes)
            : base(tokenizer, scannerResult)
        {
            this.wordNodes = wordNodes;
            this.SetID("WordScanner");
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Gets or sets the word nodes.
        /// </summary>
        public WordNode[] WordNodes
        {
            get
            {
                return this.wordNodes;
            }
            set
            {
                if (value != this.wordNodes)
                {
                    this.wordNodes = value;
                }
            }
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <returns>A new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation class.</returns>
        public override IScanner Create(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
        {
            return new WordScanner(tokenizer, scannerResult);
        }

        /// <summary>
        /// Loads the state of the scanner.
        /// </summary>
        /// <param name="state">An <see cref="System.Object"/> that contains the state of the scanner.</param>
        public override void LoadState(object state)
        {
            XElement element = (XElement)state;

            IEnumerable<XElement> wordGroupRootList = from el in element.Descendants("wordGroups").Descendants()
                                                      where el != null && (el.Name == "setting" &&
                                                      el.Attributes().Where(att => att.Name == "name").FirstOrDefault() != null)
                                                      select el;

            this.wordNodes = new WordNode[wordGroupRootList.Count()];
            for (int i = 0; i < wordGroupRootList.Count(); i++)
            {
                XElement x = wordGroupRootList.ElementAt(i);
                // Load settings.
                WordNode node = new WordNode();
                switch (x.Name.ToString())
                {
                    case "BackColor":
                        node.BackColor = ColorTranslator.FromHtml(x.Value);
                        break;
                    case "ForeColor":
                        node.ForeColor = ColorTranslator.FromHtml(x.Value);
                        break;
                    case "Font":
                        node.Font = x.Value;
                        break;
                    case "NavigateUrl":
                        node.NavigateUrl = x.Value;
                        break;
                    case "IgnoreCase":
                        bool ignoreCase;
                        bool.TryParse(x.Value, out ignoreCase);
                        node.IgnoreCase = ignoreCase;
                        break;
                }
                List<string> entitiesList = (from el in x.Element("entities").Descendants("entity")
                                            select el.Value).ToList();
                // Load entities.
                foreach (string s in entitiesList)
                    node.Entities.Add(s);

                this.wordNodes[i] = node;
            }
        }

        /// <summary>
        /// Resets the scanner.
        /// </summary>
        public override void Reset()
        {
            this.lastToken = null;
        }

        /// <summary>
        /// Saves the current state of the scanner.
        /// </summary>
        /// <param name="container">The container which will contain the state.</param>
        /// <returns>An <see cref="System.Object"/> that contains the state of the scanner.</returns>
        public override object SaveState(object container)
        {
            //XDocument document = (XDocument)container;
            XElement element = (XElement)base.SaveState(container);

            XElement wordRootElement = new XElement("wordGroups");
            element.Add(wordRootElement);
            XElement wordElement;
            XElement settingRootElement;
            XElement entityRootElement;
            XElement entityElement;
            foreach (WordNode node in this.wordNodes)
            {
                wordElement = new XElement("wordGroup");
                wordRootElement.Add(wordElement);

                // Save settings.
                settingRootElement = new XElement("settings");
                wordElement.Add(settingRootElement);

                settingRootElement.Add(this.CreateSetting("BackColor", ColorTranslator.ToHtml(node.BackColor)));
                settingRootElement.Add(this.CreateSetting("ForeColor", ColorTranslator.ToHtml(node.ForeColor)));
                settingRootElement.Add(this.CreateSetting("Font", node.Font));
                settingRootElement.Add(this.CreateSetting("NavigateUrl", node.NavigateUrl));
                settingRootElement.Add(this.CreateSetting("IgnoreCase", node.IgnoreCase.ToString()));

                // Save entities.
                entityRootElement = new XElement("entities");
                wordElement.Add(entityRootElement);
                foreach (string entity in node.Entities)
                {
                    entityElement = new XElement("entity",entity);
                    entityRootElement.Add(entityElement);
                }
            }

            return element;
        }

        /// <summary>
        /// Scans a token.
        /// </summary>
        /// <remarks>
        /// An <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation will generally have a reference to a 
        /// <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.NodeCollection"/> which will be used to store results of a scan.
        /// </remarks>
        /// <param name="token">A token from the source code.</param>
        public override void Scan(string token)
        {
            if (!this.Enabled)
            {
                if (this.Child != null)
                {
                    this.Child.Scan(token);
                }
            }
            else
            {
                bool isMatch = false;

                if (this.lastToken == null || (this.lastToken != token && this.IsValidChar(this.lastToken[0])))
                {
                    string currentWord = String.Empty;
                    for (int i = this.Tokenizer.Position; i < this.Tokenizer.Source.Length; i++)
                    {
                        if (!this.IsValidChar(this.Tokenizer.Source[i]))
                            currentWord += this.Tokenizer.Source[i];
                        else
                            break;
                    }

                    if (currentWord.Length > 0)
                    {
                        for (int i = 0; i < this.wordNodes.Length; i++)
                        {
                            for (int j = 0; j < this.wordNodes[i].Entities.Count; j++)
                            {
                                if (this.wordNodes[i].Entities[j].Length == currentWord.Length)
                                {
                                    StringComparison sc = StringComparison.InvariantCulture;
                                    if (this.wordNodes[i].IgnoreCase)
                                        sc = StringComparison.InvariantCultureIgnoreCase;
                                    if (String.Compare(currentWord, this.wordNodes[i].Entities[j], sc) == 0)
                                    {
                                        isMatch = true;
                                        this.ScannerResult.Add(new Occurrence(this.Tokenizer.Position, this.wordNodes[i].Entities[j].Length, this.wordNodes[i]));
                                        this.Tokenizer.MoveTo(this.Tokenizer.Position + this.wordNodes[i].Entities[j].Length - 1);
                                        break;
                                    }
                                }
                            }
                            if (isMatch)
                                break;
                        }
                    }
                }

                if (!isMatch)
                {
                    if (this.Child != null)
                    {
                        this.Child.Scan(token);
                    }
                }
            }

            this.lastToken = token;
        }

        /// <summary>
        /// Creates a steting.
        /// </summary>
        /// <param name="document">The document which will contain the setting.</param>
        /// <param name="name">The name of the setting.</param>
        /// <param name="value">The value of the setting.</param>
        /// <returns>The <see cref="System.Xml.XmlElement"/> which represents the setting.</returns>
        private XElement CreateSetting(string name, string value)
        {
            return new XElement("setting",value, new XAttribute("name", name));
        }

        /// <summary>
        /// Checks whether the valid is a valid char in the sense that it can be placed in front or after a word.
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private bool IsValidChar(char c)
        {
            return (!char.IsLetter(c)) && (c != '_');
        }

        #endregion Methods
    }
}